docs: lifecycle events README section [4/4]#33
Merged
Conversation
Foundational types for the application-lifecycle events feature. Pure additions; no behavior change to MetaRouterAnalyticsClient yet. - LifecycleStorage: SharedPreferences wrapper for (version, build) under com.metarouter.analytics.lifecycle, separate from identity prefs so reset() does not wipe install/update state - IdentityStorage.hasAnyValue(): identifies users that pre-date the lifecycle SDK (existing identity, no lifecycle keys) so the tracker can emit Application Updated instead of Application Installed - IdentityManager.hasAnyValue(): forwarder - AppContext.fromContext(): single-source-of-truth factory that reads PackageManager once. The lifecycle subsystem and DeviceContextProvider will share the cached snapshot so per-event enrichment never re-reads the manifest - LifecycleEventNames / LifecycleEventProperties: extracted constants used by the tracker and (later) the coordinator Refs: sc-38233
Two small follow-ups from code review on slice 1. - AppContext.fromContext: when the outer PackageManager read fails entirely, derive a best-effort `name` from the last segment of the package id rather than returning literal 'unknown'. Better attribution on ingest dashboards in the rare case the read throws (corrupted install / instrumentation contexts) and matches what most launchers show when applicationLabel resolution fails. - AppContextTest: direct Robolectric coverage for the factory. Slice 1 introduces fromContext standalone — it had no direct tests until now, only transitive coverage via DeviceContextProvider. Refs: sc-38233
The lifecycle event tracker — install/update detection, cold-launch sequencing, foreground/background transitions, and the one-shot deep-link buffer. Standalone class; not yet wired into MetaRouterAnalyticsClient (slice 3 does the wiring). Cold-launch decision tree: - no persisted state, no identity → Application Installed - no persisted state, identity present → Application Updated with previous_version/previous_build = 'unknown' (SDK upgrade) - persisted (version, build) matches → no install/update event - persisted (version, build) differs → Application Updated with prior values Cold-launch Opened ordering depends on the foreground-state probe: - foreground at SDK init → emit Opened immediately and suppress the imminent ProcessLifecycleOwner.onStart so we don't double-emit - background at SDK init (silent push, JobScheduler, WorkManager) → defer Opened to the first true ON_START transition, which then emits with from_background=false as the cold-launch bridge Deep-link buffer is one-shot and last-write-wins. Multiple openURL calls before the next Opened keep only the most recent URL; the buffer clears once attached to an Opened payload. The tracker has no on/off switch — when the feature is disabled the host (LifecycleCoordinator, slice 3) simply never constructs an instance. AppContext is injected (the cached snapshot from slice 1) so the tracker never reads PackageManager itself. Refs: sc-38234
Three follow-ups from code review on slice 2. - defaultForegroundCheck: narrow the catch from Throwable to IllegalStateException + NoClassDefFoundError. The wide catch swallowed OOM / StackOverflowError silently. ProcessLifecycleOwner.get() throws ISE off the main thread; the lifecycle-process artifact may be stripped in test setups, hence NoClassDefFoundError. Anything else surfaces as a real bug. - onSdkReady: AtomicBoolean idempotency guard. If the host accidentally invokes the cold-launch sequence twice (future re-init path, test misuse) we used to re-emit Installed/Updated/Opened and stomp suppressNextForeground, which would eat the next legitimate foreground transition. - KDoc: openURL / handleDeepLink reference said EXTRA_REFERRER goes through getStringExtra. Per Android docs EXTRA_REFERRER is a Uri, not a String — getStringExtra on it is essentially always null. Point hosts at Activity.referrer?.host instead. - Tests cover the new idempotency guard and the onForeground-before- onSdkReady contract (documenting what happens if the host wiring invariant breaks). Refs: sc-38234
End-to-end integration of the lifecycle subsystem. After this slice the feature is fully functional behind `InitOptions.trackLifecycleEvents` — default stays false so existing customers are not affected on upgrade. Coordinator seam - LifecycleCoordinator wraps LifecycleEventTracker. MetaRouterAnalytics- Client no longer references the tracker directly; future session / attribution work has a clear place to land alongside lifecycle events. - Constructed only when trackLifecycleEvents=true. When the feature is off, lifecycleCoordinator is null and every dispatch site is a null-safe no-op. AppContext caching - AnalyticsClient reads AppContext.fromContext() exactly once at init and injects the same snapshot into both DeviceContextProvider and LifecycleEventTracker. The per-event enrichment path no longer hits PackageManager. Public API - handleDeepLink renamed to openURL across AnalyticsInterface, AnalyticsProxy, PendingCall, and the proxied call replay. Name matches Segment's iOS SDK and avoids the over-promise of 'handle' (the SDK does not route or parse the URL — it buffers it for the next Application Opened). - openURL on the client logs Logger.warn and no-ops when the feature is disabled. Silent no-op was bad DX; misconfiguration is now diagnosable from logcat. Opt-in default - InitOptions.trackLifecycleEvents flips from true to false. KDoc updated. InitOptionsTest renamed to assert the new default. Lifecycle ordering - onBackground emits Application Backgrounded BEFORE flush / flushToDisk so the event lands in the same drain (unchanged behavior; comment kept next to the code). Refs: sc-38235
Slice 3 follow-ups from code review. - onForeground ordering: dispatcher.resume() now runs BEFORE coordinator.onForeground(). Mirrors iOS so the resumed dispatcher picks up the just-emitted Application Opened in its next tick rather than waiting for the following flush cycle. Functionally benign on Android (track() enqueues regardless of dispatcher state) but the cross-platform parity contract was an explicit constraint. - Coordinator gate: trackLifecycleEvents is now the sole on/off signal. Previously a test seam injection of injectedLifecycleCoordinator could install a coordinator while the flag was false. Production paths were fine via MetaRouter.kt, but the off-state should be structurally enforced regardless of the seam. - AnalyticsInterface.openURL KDoc: drop the misleading Intent.EXTRA_REFERRER + getStringExtra suggestion. Per Android docs EXTRA_REFERRER is a Uri (returns null via getStringExtra). Point hosts at Activity.referrer?.host, which is the canonical API for the calling-app host. - DeviceContextProvider: KDoc on the appContext constructor default noting it exists for test ergonomics only. Production code must pass the explicit cached snapshot. Refs: sc-38235
382bcd4 to
4e7a426
Compare
Adds the user-facing documentation for the lifecycle events feature shipped by sc-38233 / sc-38234 / sc-38235. - Top-level Lifecycle Events section with the four events table, cold-launch sequencing rules, persistence semantics, and the rationale for cold-launch suppression on background-launched processes (silent push, JobScheduler, etc.) - Opt-in framing: trackLifecycleEvents defaults to false, sample of how to enable, note that openURL is a no-op when disabled - Deep-link wiring snippets for Activity.onCreate / onNewIntent and App Links via verified intent filters - Privacy guidance with a URL-sanitization sample (auth tokens, OTPs, magic-link params) - 'Why no auto-instrumentation' rationale (no manifest mutation, no ActivityLifecycleCallbacks proxy, host control) - TOC entry and openURL listed in the Analytics Interface API ref Refs: sc-38236
Slice 4 follow-ups from code review. - Activity entry-point snippet: drop the misleading intent.getStringExtra(Intent.EXTRA_REFERRER) suggestion. EXTRA_REFERRER is documented as a Uri, not a String, so the String overload always returns null. Use Activity.referrer?.host (the canonical API) instead. - Add an explicit imports block to the Activity snippet so it's copy-paste-runnable. - Soften intent.data!! in the privacy sample to a guarded let — a privacy-themed example shouldn't crash on a bare bang. - Add the android.net.Uri import to the sanitize() snippet for the same reason. Refs: sc-38236
e6482b0 to
fb9ad90
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Shortcut: sc-38236
Parent: sc-36799
iOS reference: sc-38231
Slice 4 of 4 — README documentation. No behavior change.
Summary
User-facing documentation for the lifecycle-events feature shipped by slices 1–3.
## Lifecycle Eventssection with the four-event table, cold-launch sequencing, persistence semantics, and rationale for cold-launch suppression on background-launched processes (silent push,JobScheduler, etc.).trackLifecycleEventsdefaults tofalse, sample of how to enable, note thatopenURL(...)is a no-op when disabled.Activity.onCreate/onNewIntentand App Links via verified intent filters.ActivityLifecycleCallbacksproxy, host control).openURLlisted in the Analytics Interface API ref.Stack
LifecycleEventTrackeralgorithm + testsopenURLpublic APITest plan
## Lifecycle Eventssection)./gradlew :metarouter-sdk:teststill passes (no behavior change)